#include "HerzeleidPrecompiled.hxx"
#include "HerzeleidLoader.hxx"
using namespace HerzeleidRendererResourcesNamespaceImpl;

#ifdef HERZELEID_DESKTOP
#define GetStringData( pStr ) pStr->data()
#define GetStringLength( pStr ) pStr->size()
#define StringAt( pStr, iIndex ) (*pStr.get())[iIndex]
#define ToLowerInvariant towlower
#define DDSExtension (L"dds") 
#define DotChar (L'.')
#define IsDDSExtension( pStr ) ((lstrcmp( GetExtension(pStr)->data(), DDSExtension)) == 0)
#else
#define GetStringData( pStr ) pStr->Data()
#define GetStringLength( pStr ) pStr->Length()
#define StringAt( pStr, iIndex ) pStr[iIndex]
#define ToLowerInvariant tolower
#define DDSExtension ("dds")
#define DotChar ('.')
#define IsDDSExtension( pStr ) (pStr == DDSExtension)
#endif

BasicLoader::BasicLoader(
		_In_ Device3Ptr pDevice
		)
{
#ifdef HERZELEID_DESKTOP
	m_Reader = UniquePtrReader( new Reader() );
#else
	m_Reader = ref new Reader();
#endif

	m_Device = pDevice;
}

BasicLoader::StringArg BasicLoader::GetExtension( 
	_In_ BasicLoader::StringArg wszFileName 
	)
{
	auto iLastDot = (-1);
	auto iLength = GetStringLength(wszFileName);
	for ( auto i = iLength; i >= 0; i++ )
		if ( *(GetStringData(wszFileName) + i) == DotChar )
			iLastDot = i;

	if ( iLastDot != (-1) )
	{
		auto iExtensionLength = iLength - iLastDot;
#ifdef HERZELEID_DESKTOP
		BasicLoader::StringArg wszExtension(
			new String16(new Char16[iExtensionLength])
			);
		for ( auto i = 0u; i < iExtensionLength; i++ )
		{
			auto iOffset = iLastDot + 1 + i; // Get the offset 
			StringAt(wszExtension, i) = ToLowerInvariant( // Fill out the extension string
				*(GetStringData(wszFileName) + iOffset) 
				);
		}
#else
		BasicLoader::StringArg wszExtension = "";
		for ( auto i = 0u; i < iExtensionLength; i++ )
		{
			auto iOffset = iLastDot + 1 + i; // Get the offset 
			wszExtension += ToLowerInvariant( // Fill out the extension string
				*(GetStringData(wszFileName) + iOffset) 
				);
		}
#endif

		return( wszExtension );
	}

	return nullptr;
}

Void BasicLoader::CreateTexture(
	_In_ Bool bDecodeAsDDS,
    _In_reads_bytes_(uDataSize) BytePtr pData,
    _In_ UInt32 uDataSize,
    _Out_opt_ Texture2Address ppTexture,
    _Out_opt_ ShaderResourceViewAddress ppView,
    _In_opt_ BasicLoader::StringArg wszName
	)
{
	WRL::ComPtr<ShaderResourceView> lView;
	WRL::ComPtr<Texture2> lTex;

	if ( bDecodeAsDDS )
	{
		WRL::ComPtr<Resource> lResource;

		if ( !ppView )
		{
			DirectX::ThrowIfFailed(DirectX::CreateDDSTextureFromMemory(
				m_Device,
				pData,
				uDataSize,
				lResource.GetAddressOf(),
				nullptr
				));
		}
		else
		{
			DirectX::ThrowIfFailed(DirectX::CreateDDSTextureFromMemory(
				m_Device,
				pData,
				uDataSize,
				lResource.GetAddressOf(),
				lView.GetAddressOf()
				));
		}
	}
	else
	{
		// WIC is not available for Phone (all the D2D actually). 
		// TODO: #ifndef "Phone" => Implement for Metro and Desktop. #endif
		DirectX::ThrowIfFailed(
			E_NOTIMPL
			);
	}

	if ( GetStringLength(wszName) ) 
	{
		SetDebugName( 
			(lTex.Get()), // Shader address to set the debug name to
			GetStringData(wszName) // Debug name
			);
	}
	else
	{
		char szName[] = "BasicLoaderTexture";
		SetDebugName( 
			(lTex.Get()), // Shader address to set the debug name to
			GetStringData(wszName) // Debug name
			);
	}

    if ( ppTexture != nullptr ) (*ppTexture) = lTex.Detach();
    if ( ppView != nullptr ) (*ppView) = lView.Detach();
}

Void BasicLoader::CreateInputLayout(
    _In_reads_bytes_(uBytecodeSize) BytePtr pBytecode,
    _In_ UInt32 uBytecodeSize,
    _In_reads_opt_(uLayoutDescNumElements) D3D11_INPUT_ELEMENT_DESC* pLayoutDescElements,
    _In_ UInt32 uLayoutDescNumElements,
    _Out_ InputLayoutAddress ppLayout,
    _In_opt_ BasicLoader::StringArg wszName
    )
{
    if ( !pLayoutDescElements )
    {
        // If no input layout is specified, use the BasicVertex layout.
        const D3D11_INPUT_ELEMENT_DESC aBasicVertexLayoutDesc[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        DirectX::ThrowIfFailed(
            m_Device->CreateInputLayout(
                aBasicVertexLayoutDesc,
                ARRAYSIZE(aBasicVertexLayoutDesc),
                pBytecode,
                uBytecodeSize,
                ppLayout
                )
            );
    }
    else
    {
        DirectX::ThrowIfFailed(
            m_Device->CreateInputLayout(
                pLayoutDescElements,
                uLayoutDescNumElements,
                pBytecode,
                uBytecodeSize,
                ppLayout
                )
            );
    }

	if ( GetStringLength(wszName) ) 
	{
		SetDebugName( (*ppLayout), GetStringData(wszName) );
	}
	else
	{
		char szName[] = "BasicLoaderInputLayout";
		SetDebugName( (*ppLayout), szName );
	}
}



BasicLoader::Action BasicLoader::LoadTexture(
	_In_ BasicLoader::StringArg wszFileName, 
    _Outptr_opt_result_nullonfailure_ Texture2Address ppTexture,
    _Outptr_opt_result_nullonfailure_ ShaderResourceViewAddress ppView
	)
{
	auto TexBuffer = m_Reader->ReadData(wszFileName);
	return CreateTexture(
		IsDDSExtension(wszFileName),
		TexBuffer->Data,
		TexBuffer->Length,
		ppTexture,
		ppView,
		wszFileName
		);
}

BasicLoader::AsyncAction BasicLoader::LoadTextureAsync(
	_In_ BasicLoader::StringArg wszFileName, 
    _Outptr_opt_result_nullonfailure_ Texture2Address ppTexture,
    _Outptr_opt_result_nullonfailure_ ShaderResourceViewAddress ppView
	)
{
	return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr TexBuffer)
	{
		return CreateTexture(
			IsDDSExtension(wszFileName),
			TexBuffer->Data,
			TexBuffer->Length,
			ppTexture,
			ppView,
			wszFileName
			);
	});
}

// !!!
// The most complex cases here : Vertex shader and Geometry shader

// Vertex shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ BasicLoader::StringArg wszFileName,
    _In_reads_opt_(layoutDescNumElements) D3D11_INPUT_ELEMENT_DESC pLayoutDescElements[],
    _In_ UInt32 uLayoutDescNumElements,
    _Outptr_result_nullonfailure_ VertexShaderAddress ppShader,
    _Outptr_opt_result_nullonfailure_ InputLayoutAddress ppLayout
    )
{
	auto Bytecode = m_Reader->ReadData(wszFileName);
    DirectX::ThrowIfFailed(
        m_Device->CreateVertexShader(
            Bytecode->Data,
            Bytecode->Length,
            nullptr,
            ppShader
            )
        );

    SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);

    if ( ppLayout != nullptr)
    {
        CreateInputLayout(
            Bytecode->Data,
            Bytecode->Length,
            pLayoutDescElements,
            uLayoutDescNumElements,
            ppLayout,
			wszFileName
            );
    }
}

// Vertex shader

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ BasicLoader::StringArg wszFileName,
    _In_reads_opt_(layoutDescNumElements) D3D11_INPUT_ELEMENT_DESC pLayoutDescElements[],
    _In_ UInt32 uLayoutDescNumElements,
    _Outptr_result_nullonfailure_ VertexShaderAddress ppShader,
    _Outptr_opt_result_nullonfailure_ InputLayoutAddress ppLayout
    )
{
	typedef std::vector<D3D11_INPUT_ELEMENT_DESC> LayoutDescElements;
	typedef std::shared_ptr<LayoutDescElements> SharedLayoutDescElements;
	typedef std::vector<String8> SemanticNames;
	typedef std::shared_ptr<SemanticNames> SharedSemanticNames;

	// !!!
    // This method assumes that the lifetime of input arguments may be shorter than the duration of this task.  
	// In order to ensure accurate results, a copy of all arguments passed by pointer must be made.  The method 
	// then ensures that the lifetime of the copied data exceeds that of the task.

    // Create copies of the layoutDesc array as well as the SemanticName strings, both of which are pointers to 
	// data whose lifetimes may be shorter than that of this method's task.

	SharedLayoutDescElements LayoutDescElementsCopy;
	SharedSemanticNames LayoutDescElementsNamesCopy;

	if ( !pLayoutDescElements )
    {
        LayoutDescElementsCopy.reset(
            new LayoutDescElements(
                pLayoutDescElements, // First
                pLayoutDescElements + uLayoutDescNumElements // Last
                )
            );

        LayoutDescElementsNamesCopy.reset(
            new SemanticNames(uLayoutDescNumElements)
            );

        for ( auto i = 0u; i < uLayoutDescNumElements; i++ ) // Loop through all elements
        {
            LayoutDescElementsNamesCopy->at(i).assign( // and copy data 
				pLayoutDescElements[i].SemanticName // Get semantic name
				);
        }
    }

    return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
    {
        DirectX::ThrowIfFailed(
            m_Device->CreateVertexShader(
                Bytecode->Data,
                Bytecode->Length,
                nullptr,
                ppShader
                )
            );

        SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);

        if ( !ppLayout )
        {
            if ( !pLayoutDescElements )
            {
				// !!!
                // Reassign the SemanticName elements of the layoutDesc array copy to point to the corresponding copied strings. 
				// Performing the assignment inside the lambda body ensures that the lambda will take a reference to the shared_ptr
                // that holds the data. This will guarantee that the data is still valid when CreateInputLayout is called.

                for ( auto i = 0u; i < uLayoutDescNumElements; i++)
                    LayoutDescElementsCopy->at(i).SemanticName = 
						LayoutDescElementsNamesCopy->at(i).c_str();
            }

            CreateInputLayout(
                Bytecode->Data, // buffered data
                Bytecode->Length, // buffer length 
                pLayoutDescElements != nullptr 
					? LayoutDescElementsCopy->data() 
					: nullptr,
                uLayoutDescNumElements,
                ppLayout,
				wszFileName
                );
        }
    });
}

// Geometry shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ BasicLoader::StringArg wszFileName,
    _In_reads_opt_(uNumEntries) const D3D11_SO_DECLARATION_ENTRY streamOutDeclaration[],
    _In_ UInt32 uNumEntries,
    _In_reads_opt_(uNumStrides) const UInt32 pBufferStrides[],
    _In_ UInt32 uNumStrides,
    _In_ UInt32 uRasterizedStream,
    _Outptr_result_nullonfailure_ GeometryShaderAddress ppShader
    )
{
    auto Bytecode = m_Reader->ReadData(wszFileName);

    DirectX::ThrowIfFailed(
        m_Device->CreateGeometryShaderWithStreamOutput(
            Bytecode->Data,
            Bytecode->Length,
            streamOutDeclaration,
            uNumEntries,
            pBufferStrides,
            uNumStrides,
            uRasterizedStream,
            nullptr,
            ppShader
            )
        );

   SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);
}

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ BasicLoader::StringArg wszFileName,
    _In_reads_opt_(uNumEntries) const D3D11_SO_DECLARATION_ENTRY pStreamOutDeclaration[],
    _In_ UInt32 uNumEntries,
    _In_reads_opt_(uNumStrides) const UInt32 pBufferStrides[],
    _In_ UInt32 uNumStrides,
    _In_ UInt32 uRasterizedStream,
    _Outptr_result_nullonfailure_ GeometryShaderAddress ppShader
    )
{
	typedef std::vector<D3D11_SO_DECLARATION_ENTRY> StreamOutputDeclEntryElements;
	typedef std::shared_ptr<StreamOutputDeclEntryElements> SharedStreamOutputDeclEntryElements;
	typedef std::vector<String8> SemanticNames;
	typedef std::shared_ptr<SemanticNames> SharedSemanticNames;
	typedef std::vector<UInt32> BufferStrides;
	typedef std::shared_ptr<BufferStrides> SharedBufferStrides;

	// !!!
    // This method assumes that the lifetime of input arguments may be shorter than the duration of this task.  
	// In order to ensure accurate results, a copy of all arguments passed by pointer must be made.  The method 
	// then ensures that the lifetime of the copied data exceeds that of the task.

    // Create copies of the layoutDesc array as well as the SemanticName strings, both of which are pointers to 
	// data whose lifetimes may be shorter than that of this method's task.

	SharedStreamOutputDeclEntryElements StreamOutDeclarationCopy;
	SharedSemanticNames StreamOutDeclarationNamesCopy;

	if ( !pStreamOutDeclaration )
    {
        StreamOutDeclarationCopy.reset(
            new StreamOutputDeclEntryElements(
                pStreamOutDeclaration, // First
                pStreamOutDeclaration + uNumEntries // Last
                )
            );

        StreamOutDeclarationNamesCopy.reset(
            new SemanticNames(uNumEntries)
            );

        for ( auto i = 0u; i < uNumEntries; i++ ) // Loop through all elements
        {
            StreamOutDeclarationNamesCopy->at(i).assign( // and copy data 
				pStreamOutDeclaration[i].SemanticName // Get semantic name
				);
        }
    }
    
	// !!!
    // Create a copy of the buffer strides array, which is a pointer to data
    // whose lifetime may be shorter than that of this method's task.

    SharedBufferStrides BufferStridesCopy;
    if ( !pBufferStrides )
    {
        BufferStridesCopy.reset(
            new BufferStrides(
                pBufferStrides,
                pBufferStrides + uNumStrides
                )
            );
    }

    return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
    {
        if ( !pStreamOutDeclaration )
        {
			// !!!
            // Reassign the SemanticName elements of the stream out declaration array copy to
            // point to the corresponding copied strings. Performing the assignment inside the
            // lambda body ensures that the lambda will take a reference to the shared_ptr
            // that holds the data.  This will guarantee that the data is still valid when
            // CreateGeometryShaderWithStreamOutput is called.

            for ( auto i = 0u; i < uNumEntries; i++ )
            {
                StreamOutDeclarationCopy->at(i).SemanticName = 
					StreamOutDeclarationNamesCopy->at(i).c_str();
            }
        }

        DirectX::ThrowIfFailed(
            m_Device->CreateGeometryShaderWithStreamOutput(
                Bytecode->Data,
                Bytecode->Length,
                pStreamOutDeclaration != nullptr 
					? StreamOutDeclarationCopy->data() 
					: nullptr,
                uNumEntries,
                pBufferStrides != nullptr 
					? BufferStridesCopy->data() 
					: nullptr ,
                uNumStrides,
                uRasterizedStream,
                nullptr,
                ppShader
                )
            );

	   SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);
    });
}

// Hull shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ HullShaderAddress ppShader
    )
{
    auto Bytecode = m_Reader->ReadData(wszFileName);
    DirectX::ThrowIfFailed(
        m_Device->CreateHullShader(
            Bytecode->Data,
            Bytecode->Length,
            nullptr,
            ppShader
            )
        );

   SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);
}

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ HullShaderAddress ppShader
    )
{
   return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
   {
	    DirectX::ThrowIfFailed(
			m_Device->CreateHullShader(
				Bytecode->Data,
				Bytecode->Length,
				nullptr,
				ppShader
				)
			);

		SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);
   });
}

// Domain shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ DomainShaderAddress ppShader
    )
{
    auto Bytecode = m_Reader->ReadData(wszFileName);
    DirectX::ThrowIfFailed(
        m_Device->CreateDomainShader(
            Bytecode->Data,
            Bytecode->Length,
            nullptr,
            ppShader
            )
        );

	SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);
}

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ DomainShaderAddress ppShader
    )
{
   return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
   {
	    DirectX::ThrowIfFailed(
			m_Device->CreateDomainShader(
				Bytecode->Data,
				Bytecode->Length,
				nullptr,
				ppShader
				)
			);

		SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);
   });
}

// Pixel shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ PixelShaderAddress ppShader
    )
{
    auto Bytecode = m_Reader->ReadData(wszFileName);
    DirectX::ThrowIfFailed(
        m_Device->CreatePixelShader(
            Bytecode->Data,
            Bytecode->Length,
            nullptr,
            ppShader
            )
        );

	SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);
}

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ PixelShaderAddress ppShader
    )
{
   return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
   {
	    DirectX::ThrowIfFailed(
			m_Device->CreatePixelShader(
				Bytecode->Data,
				Bytecode->Length,
				nullptr,
				ppShader
				)
			);

		SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);
   });
}

// Compute shader

BasicLoader::Action BasicLoader::LoadShader(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ ComputeShaderAddress ppShader
    )
{
    auto Bytecode = m_Reader->ReadData(wszFileName);
    DirectX::ThrowIfFailed(
        m_Device->CreateComputeShader(
            Bytecode->Data,
            Bytecode->Length,
            nullptr,
            ppShader
            )
        );

	SetDebugName( 
		(*ppShader), // Shader address to set the debug name to
		GetStringData(wszFileName) // Debug name
		);
}

BasicLoader::AsyncAction BasicLoader::LoadShaderAsync(
    _In_ StringArg wszFileName,
    _Outptr_result_nullonfailure_ ComputeShaderAddress ppShader
    )
{
   return m_Reader->ReadDataAsync(wszFileName).then( [=]( _In_ Reader::ByteArrayConstPtr Bytecode )
   {
	    DirectX::ThrowIfFailed(
			m_Device->CreateComputeShader(
				Bytecode->Data,
				Bytecode->Length,
				nullptr,
				ppShader
				)
			);

		SetDebugName( 
			(*ppShader), // Shader address to set the debug name to
			GetStringData(wszFileName) // Debug name
			);
   });
}
